package org.moonlight.jvm.instructions.references.invoke;

import org.moonlight.jvm.instructions.base.BytecodeReader;
import org.moonlight.jvm.instructions.base.MethodInvokeLogic;
import org.moonlight.jvm.rtda.exclusive.Frame;
import org.moonlight.jvm.rtda.share.Object;
import org.moonlight.jvm.rtda.share.heap.classmember.Method;
import org.moonlight.jvm.rtda.share.heap.symref.InterfaceMethodRef;
import org.moonlight.jvm.rtda.share.heap.symref.MethodRef;
import org.moonlight.jvm.rtda.share.methodarea.RtConstantPool;
import org.moonlight.jvm.utils.MethodLookupUtil;

/**
 * invoke_interface指令: 执行接口方法.
 *                      和其他三条方法调用指令略有不同，在字节码中，invokeinterface指令的操作码后面跟着4字节而非2字节。
 *                      前两字节的含义和其他指令相同，是个uint16运行时常量池索引。
 *                      第3字节的值是给方法传递参数需要的slot数，其含义和给Method结构体定义的argSlotCount字段相同。
 *                      第4字节是留给Oracle的某些Java虚拟机实现用的，它的值必须是0。该字节的存在是为了保证Java虚拟机可以向后兼容。
 *
 * @author Moonlight
 * @createTime 2023/9/12 15:40
 **/
public class InvokeInterface extends MethodInvokeLogic {

    private int count;
    private int zero;

    @Override
    public void fetchOperands(BytecodeReader reader) {
        super.fetchOperands(reader);
        this.count = reader.readInt8();
        this.zero = reader.readInt8();
    }

    @Override
    public void execute(Frame frame) {
        // 当前运行时常量池
        RtConstantPool rtCp = frame.getMethod().getClazz().getRtCp();
        // 方法的符号引用
        InterfaceMethodRef methodRef = (InterfaceMethodRef) rtCp.getConstant(this.idx);
        // 根据方法符号引用解析出来的方法
        Method resolvedMethod = methodRef.resolvedInterfaceMethod();
        if (resolvedMethod.isStatic() || resolvedMethod.isPrivate()) {
            throw new IncompatibleClassChangeError();
        }
        // this 引用
        Object ref = frame.getOperandStack().GetRefFromTop(resolvedMethod.getArgSlotCount() - 1);
        if (ref == null) {
            throw new NullPointerException();
        }
        if (!ref.getClazz().isImplements(methodRef.resolvedClass())) {
            throw new IncompatibleClassChangeError();
        }
        Method methodToBeInvoked = MethodLookupUtil.lookupMethodInClass(ref.getClazz(), methodRef.getName(), methodRef.getDescriptor());
        if (methodToBeInvoked == null || methodToBeInvoked.isAbstract()) {
            throw new AbstractMethodError();
        }
        if (!methodToBeInvoked.isPublic()) {
            throw new IllegalAccessError();
        }
        invokeMethod(frame, methodToBeInvoked);
    }

}
